package blazingbubble.legacy.game;

import spark.math.Vector3;
import spark.utility.Clock;

import java.util.Vector;

public class ChainReaction
{
	private static float SPEED = 6f;
	private static int PRECISION = 10;

	private Bubble bubble;
	public Vector3[] pointsx;
	private Vector3[] velocities;
	private float[] travelTimes;
	private int currentVelocityIndex = 0;

	public ChainReaction(Bubble bubble, Vector3 destination)
	{
		constructPointsCubic(bubble.getPosition(), destination);
		initializeTravelInfo();
		this.bubble = bubble;
	}

	private void initializeTravelInfo()
	{
		velocities = new Vector3[pointsx.length - 1];
		travelTimes = new float[velocities.length];
		float currentSpeed = SPEED;
		for(int i = 0; i < pointsx.length - 1; i++)
		{
			Vector3 start = pointsx[i];
			Vector3 end = pointsx[i+1];
			velocities[i] = Vector3.substract(end, start);
			float distance = velocities[i].length();
			velocities[i].normalize();
			velocities[i].multiply(currentSpeed);
			travelTimes[i] = distance / currentSpeed;
			//currentSpeed *= 1.2;
		}
	}

	private void constructPointsCubic(Vector3 start, Vector3 end)
	{
		Vector3[] points = new Vector3[3];
		points[0] = start;
		points[1] = new Vector3((start.x + end.x) / 2, (start.y < end.y)?start.y:end.y, 0);
		points[2] = end;
		int np = points.length;           // number of points
		float[] yCoords = new float[np];        // Newton form coefficients
		float[] xCoords = new float[np];        // x-coordinates of nodes
		float y;
		float x;
		float oldy;
		float oldx;

		Vector<Vector3> pointsx2 = new Vector<Vector3>();

		// draw a border around the canvas
		//g.DrawRectangle(0,0, size().width-1, size().height-1);
		for (int i=0; i < np; i++)
		{
			xCoords[i]=points[i].x;
			yCoords[i]=points[i].y;
		}

		if (np>1)
		{
			Vector<Float> a = new Vector<Float>();
			for(int i = 0; i < np; i++)
			{
				a.add(0.0f);
			}
			float x1;
			float x2;
			float[] h = new float[np];
			for (int i=1; i<=np-1; i++)
			{
				h[i] = xCoords[i] - xCoords[i-1];
			}
			if (np>2)
			{
				float[] sub = new float[np-1];
				float[] diag = new float[np-1];
				float[] sup = new float[np-1];

				for (int i=1; i<=np-2; i++)
				{
					diag[i] = (h[i] + h[i+1])/3;
					sup[i] = h[i+1]/6;
					sub[i] = h[i]/6;
					a.set(i, (yCoords[i+1]-yCoords[i])/h[i+1]-(yCoords[i]-yCoords[i-1])/h[i]);
				}
				solveTridiag(sub,diag,sup,a,np-2);
			}
			// note that a[0]=a[np-1]=0
			// draw

			oldx=xCoords[0];
			oldy=yCoords[0];

			pointsx2.add(new Vector3(oldx, oldy, 0));

			for (int i=1; i<=np-1; i++)
			{   // loop over intervals between nodes
				for (int j=1; j<=PRECISION; j++)
				{
					x1 = (h[i]*j)/PRECISION;
					x2 = h[i] - x1;
					y = ((-a.get(i-1)/6*(x2+h[i])*x1+yCoords[i-1])*x2 +
							(-a.get(i)/6*(x1+h[i])*x2+yCoords[i])*x1)/h[i];
					x=xCoords[i-1]+x1;

					pointsx2.add(new Vector3(x, y, 0));

					oldx=x;
					oldy=y;
				}
			}
			pointsx = new Vector3[pointsx2.size()];
			int abc = 0;
            for(Vector3 aPointsx2 : pointsx2)
            {
                pointsx[abc++] = aPointsx2;
            }
		}
	}

	private void solveTridiag(float[] sub, float[] diag, float[] sup, Vector<Float> b, int n)
	{
		/*                  solve linear system with tridiagonal n by n matrix a
							using Gaussian elimination *without* pivoting
							where   a(i,i-1) = sub[i]  for 2<=i<=n
									a(i,i)   = diag[i] for 1<=i<=n
									a(i,i+1) = sup[i]  for 1<=i<=n-1
							(the values sub[1], sup[n] are ignored)
							right hand side vector b[1:n] is overwritten with solution
							NOTE: 1...n is used in all arrays, 0 is unused */
		int i;
		/*                  factorization and forward substitution */
		for(i=2; i<=n; i++)
		{
			sub[i] = sub[i]/diag[i-1];
			diag[i] = diag[i] - sub[i]*sup[i-1];
			b.set(i, b.get(i) - sub[i]*b.get(i-1));
		}
		b.set(n, b.get(n)/diag[n]);
		for(i=n-1;i>=1;i--)
		{
			b.set(i, (b.get(i) - sup[i] * b.get(i+1)) / diag[i]);
		}
	}

	private void constructPoints(Vector3 start, Vector3 end)
	{
		Vector3[] points = new Vector3[3];
		points[0] = start;
		points[1] = new Vector3((start.x + end.x) / 2, (start.x < end.x)?start.x:end.x, 0);
		points[2] = end;
		int np = points.length;           // number of points
		float[] yCoords = new float[np];        // Newton form coefficients
		float[] xCoords = new float[np];        // x-coordinates of nodes
		float y;
		float x;

		int npp = np*PRECISION ;          // number of points used for drawing
		pointsx = new Vector3[npp+1];

		// draw a border around the canvas
		//g.DrawRectangle(0,0, size().width-1, size().height-1);
		for (int i=0; i < np; i++)
		{
			xCoords[i]=points[i].x;
			yCoords[i]=points[i].y;
		}
		if (np>0)
		{
			for (int k=1; k<=np-1; k++)
			{
				for (int i=0; i<=np-1-k; i++)
				{
					yCoords[i]=(yCoords[i+1]-yCoords[i])/(xCoords[i+k]-xCoords[i]);
				}
			}

			// for equidistant points along x-axis evaluate polynomial and draw line
			//float dt = BubbleField.width /npp;
			//float dt = end.x / npp;
			float dt = ((start.x > end.x)?start.x:end.x) / npp;
			for (int k=0; k<=npp; k++)
			{
				x=k*dt;
				// evaluate polynomial at t
				y = yCoords[0];
				for (int i=1; i<=np-1; i++)
				{
					y=y*(x-xCoords[i])+yCoords[i];
				}
				// draw line
				if(k == 0)
					System.out.println(x + " " + y);
				pointsx[k] = new Vector3(x, y, 0);
			}
		}
	}

	public void update()
	{
		float elapsedTimeSeconds = Clock.getInstance().getElapsedSeconds();
		bubble.getPosition().add(Vector3.multiply(velocities[currentVelocityIndex], elapsedTimeSeconds));
		travelTimes[currentVelocityIndex] -= elapsedTimeSeconds;
		if(travelTimes[currentVelocityIndex] <= 0)
		{
			currentVelocityIndex++;
			bubble.setPosition(pointsx[currentVelocityIndex]);
		}
	}

	public Vector3 getDestination() { return pointsx[pointsx.length - 1]; }

	public Bubble getBubble()
	{
		return bubble;
	}

	public boolean isDone()
	{
		return (currentVelocityIndex >= velocities.length);
	}
}
